# DISCLAIMER OF WARRANTY
#
# This source code is provided "as is" and without warranties as to performance
# or merchantability. The author and/or distributors of this source code may have
# made statements about this source code. Any such statements do not constitute
# warranties and shall not be relied on by the user in deciding whether to use
# this source code.
# This source code is provided without any express or implied warranties
# whatsoever. Because of the diversity of conditions and hardware under which
# this source code may be used, no warranty of fitness for a particular purpose
# is offered. The user is advised to test the source code thoroughly before
# relying on it. The user must assume the entire risk of using the source code.




from xml.dom import minidom
import sys
from sys import argv
import os
import zipfile
from optparse import OptionParser

# Main class - it's responsibility is to generate HTML document
#
# How to use:
# >> doc = FmDocumentGenerator() # create object
# >> htmlDoc = docGen.analizeTree(mindMapToRead) # analize and return HTML
class FmDocumentGenerator():

    # Init global structure
    def __init__(self,xmlFile):
        try:
            #Load XML (MindMap)
            xmlTree = minidom.parse(xmlFile)
            # get root - it's the entire document
            root = xmlTree.childNodes[0]
            (title,chapters)=self.initGlobalData(root)
            self.MainStructure= (title,chapters)
            self.RootNode=root
        except Exception,err:
            print "FmDocumentGenerator - ctor error:" +str(err)

    # Used to found the main nodes (title and paragraphs)
    def initGlobalData(self,root):
        # first scan: looking for root node
        if(root.hasChildNodes()):
            nodes = root.childNodes
            for node in nodes:
                if(node.nodeType==1):
                    if(node.hasAttribute("TEXT")):
                        title = node
                        # second scan: looking for child text node
                        if (node.hasChildNodes()):
                            titleChilds=title.childNodes
                            chapters=[]
                            for child in titleChilds:
                                if(child.nodeType==1):
                                    if(child.hasAttribute("TEXT")):
                                        chapters.append(child)
                            return (title,chapters)

    # Verify if XML node is a valid Freemind text node
    def isValidTextNode(self,node):
        if(node==None):
            return False
        if(node.nodeType==None):
            return False
        if(node.nodeType==1): # real text node
            if(node.hasAttribute("TEXT")):
                return True
        return False

    # Check if node contain ICON nodes and eventually return
    # a tuple with (containIcon, iconXmlNodeList)
    # TODO: manage freemind custom icon
    def containIconNode(self,node, onlyBuiltIn=True):
        if(node==None):
            print "Node icon NONE"
            return (False,None)
        iconList=[]
        if(node.hasChildNodes()):
            for childNode in node.childNodes:
                if (childNode.nodeName=="icon"):
                    if (onlyBuiltIn):
                        if(childNode.hasAttribute("BUILTIN")):
                            iconList.append(childNode)
                    else:# TODO: manage custom icons
                        print "SORRY, not yet supported!"
        if(len(iconList)>0):
            return (True,iconList)
        return (False,None)

    # Check if node contain a node with information about FONT used
    # and eventually return a tuple with (containFont, fontXmlNode)
    def containFontNode(self,node):
        if(node==None):
            print "Node font NONE"
            return (False,None)
        if(node.hasChildNodes()):
            for childNode in node.childNodes:
                if (childNode.nodeName=="font"):
                    return (True,childNode)
        return (False,None)


    # Given an icon name, return a valid HTML IMG node
    # TODO: rewrite with minidom?
    def getHtmlIconNode(self,iconFileNameList,skipIcons=False):
        if(len(iconFileNameList)==0 or skipIcons):
            return ""
        #PATH!
        result=''
        for iconFileName in iconFileNameList:
            if (os.name=='nt'):#hack: probably windows
                result += "<img src=\'.\\icons\\" + iconFileName+".png\'>"
            else:
                result += "<img src=\'./icons/" + iconFileName+".png\'>"
        return result

    # Return a valid HTML header section
    # TODO: rewrite with minidom
    def openBaseHtmlDoc(self,titleNode):
        title = titleNode.getAttribute("TEXT")
        #HACK: TAG META is needed to right visualization of UTF8 documents
        result="<HTML><HEAD><meta content=\"text/html; charset=UTF-8\" http-equiv=\"content-type\"><TITLE>" +\
         title +"</TITLE></HEAD><BODY><H1 align=\'center\'>"+ title +"</H1>"
        return result

    # Return a valid html end document
    # TODO: rewrite with minidom?
    def closeBaseHtmlDoc(self,currentHtmlDoc):
        return currentHtmlDoc + "</BODY></HTML>"

    # Used only for test - write TEXT node
    def printText(self,node,debugMode=False):
        if(debugMode==False):
            return
        if(node==None):
            return
        if(node.nodeType==None):
            return
        if(node.nodeType==1): # real text node
            if(node.hasAttribute("TEXT")):
                try:
                    string=node.attributes["TEXT"].value
                    print node.getAttribute("TEXT")
                except:
                    print "KO - NO TEXT --> " + node.toprettyxml()
        else:
            return

    def get_paragraph_styled_node(self,color,text,isBold=False,isItalic=False,link=''):
        if(link!=""):
            text="<a href=\"" + link + "\">"+text+"</a>"
        if(isBold):
            text="<b>" +text+ "</b>"
        if(isItalic):
            text="<i>" +text+ "</i>"
        if(color==""):
            return "<p>" +text+ "</p>"
        else:
            return "<p style=\"color:" +color+ "\">" +text+ "</p>"

    def get_text_styled_node(self,color,text,isBold=False,isItalic=False,link=''):
        if(link!=""):
            text="<a href=\"" + link + "\">"+text+"</a>"
        if(isItalic):
            text="<i>"+text+"</i>"
        if(isBold):
            text="<b>" +text+ "</b>"
        if(color!=""):
            text="<span style=\"color:" +color+ "\">"+text+"</span>"
        return text+"<br/>"

    def get_html_styled_node(self,color,text,isParagraph=True,isBold=False,isItalic=False,withP=True,link=''):
        if(isParagraph):
            if(withP):
                #print "WITHP"
                return self.get_paragraph_styled_node(color,text,isBold,isItalic,link)
            else:
                #print "PLAIN TEXT"
                return self.get_text_styled_node(color,text,isBold,isItalic,link)
        else:
            if(color==""):
                return "<h2>" +text+ "</h2>"
            else:
                return "<h2 style=\"color:" +color+ "\">" +text+ "</h2>"

    # Translate every XML node into HTML node
    # TODO: rewrite with minidom?
    def traslateNodeToHTML(self,node,chapters,skipIcons=False,singleParagraph=True):
        if(self.isValidTextNode(node)==True):
            try:
                #HACK: in this way, get also the images (freemind implementation)
                string=node.attributes["TEXT"].value
                #HACK: get also color!
                color=""
                if(node.hasAttribute("COLOR")):
                    color = node.attributes["COLOR"].value
                #link management
                link = None
                if(node.hasAttribute("LINK")):
                    link = node.attributes["LINK"].value
                #icons managemnt - ONLY INTERNAL
                (containIcon,iconNodeList) = self.containIconNode(node)
                iconName=""
                iconNameList=[]
                if(containIcon and iconNodeList!=None):
                    for iconNode in iconNodeList:
                        iconName=iconNode.attributes["BUILTIN"].value
                        iconNameList.append(iconName)
                # font management
                # TODO: extend to support FONT and SIZE
                bold=False
                italic=False #support to ITALIC
                (containFont, fontNode) = self.containFontNode(node)
                if(containFont and fontNode!=None):
                    if(fontNode.hasAttribute("BOLD")):
                        bold = bool(fontNode.attributes["BOLD"].value)
                    if(fontNode.hasAttribute("ITALIC")):
                        italic = bool(fontNode.attributes["ITALIC"].value)

                result =""
                if node in chapters:
                    result = self.get_html_styled_node(color,self.getHtmlIconNode(iconNameList,skipIcons)+ string,False,bold,italic,singleParagraph)
                else:
                    # HACK: excluding title again
                    title=self.MainStructure[0].attributes["TEXT"].value
                    if(string != title):
                        if link==None:
                            result = self.get_html_styled_node(color,self.getHtmlIconNode(iconNameList,skipIcons)+ string,True,bold,italic,singleParagraph)
                        else:
                            # html link tag
                            result = self.get_html_styled_node(color,self.getHtmlIconNode(iconNameList,skipIcons)+ string,True,bold,italic,singleParagraph,link)
                return result
            except Exception, err:
                print "traslateNodeToHTML ERROR: " + str(err)
        return ""


    # Scan recursively the Mind map xml, and populate a list with HTML nodes generated
    def navigateTree(self,node,htmlNodeList,debugMode=False,skipIcons=False):
        chapters=self.MainStructure[1]
        #base: leafs
        if(node.hasChildNodes()==False):
            self.printText(node,debugMode)
            htmlNode = self.traslateNodeToHTML(node,chapters,skipIcons)
            htmlNodeList.append(htmlNode)
            return
        else: # inductive step: branches
            childNodes = node.childNodes
            for childNode in childNodes:
                #print text for debug...
                if(childNode.hasChildNodes()):
                    self.printText(childNode,debugMode)
                    htmlNode = self.traslateNodeToHTML(childNode,chapters,skipIcons)
                    htmlNodeList.append(htmlNode)
                #...and navigate its children
                self.navigateTree(childNode,htmlNodeList,debugMode,skipIcons)

    def scanRecursivelyChapter(self,node,resultList,debugMode,skipIcons):
        chapters=self.MainStructure[1]
        if(node.hasChildNodes()==False):
            self.printText(node,debugMode)
            htmlNode = self.traslateNodeToHTML(node,chapters,skipIcons,False)
            if(htmlNode!=None):
                resultList.append(htmlNode)
            return
        else:
            for childNode in node.childNodes:
                if(childNode.hasChildNodes()):
                    self.printText(childNode,debugMode)
                    htmlNode = self.traslateNodeToHTML(childNode,chapters,skipIcons,False)
                    if(htmlNode!=None):
                        resultList.append(htmlNode)
                self.scanRecursivelyChapter(childNode,resultList,debugMode,skipIcons)

    # Scan iteratively the mind map, and create a list of nodes
    def iterateTree(self,htmlNodeList,debugMode=False,skipIcons=False):
        chapters=self.MainStructure[1]
        for chapter in chapters:
            #translate chapter into title
            htmlNodeList.append(self.traslateNodeToHTML(chapter,chapters,skipIcons))

            #print "Processing chapter: " + chapter.toprettyxml()
            resultList=[]
            result="<p>"
            self.scanRecursivelyChapter(chapter,resultList,debugMode,skipIcons)
            for text in resultList:
                if(text!=None and text!=''):
                    result+=text
            result+="</p>"
            htmlNodeList.append(result)

    # Main function: load Freemind XML, initialize global data structure,
    # scans mind map and create HTML document as string
    def analizeTree(self,debugMode=False,skipIcons=False,singleParagraph=False):
        try:
            # create head of HTML
            htmlDoc = self.openBaseHtmlDoc(self.MainStructure[0])
            # recursively create document
            htmlNodeList=[]
            if(singleParagraph):
                self.iterateTree(htmlNodeList,debugMode,skipIcons)
            else:
                self.navigateTree(self.RootNode,htmlNodeList,debugMode,skipIcons)
            for htmlNode in htmlNodeList:
                htmlDoc+=htmlNode
            #close HTML document
            htmlDoc = self.closeBaseHtmlDoc(htmlDoc)
            return htmlDoc
        except Exception, err:
            print "ERROR on XML Analisys: %s" % str(err)
            return None

# It's responsibility is give file support to Document Generation process
# Now, it only manage ZIP file with ICONS used into mind map
class FileManager():

    # Need inputFileName to set Destination Path of icons decompressed
    def __init__(self, inputFileName):
        self.DestPath=os.path.dirname(inputFileName)

    # Write data into file and convert them into UTF8
    def writeHtmlInUTF8(self,dataToWrite,fileName):
        # HACK: in this way, replace invalid ASCII caracters with ?
        #htmlDoc = htmlDoc.encode('ascii','replace')
        #HACK: right way to manage encoding UTF8
        if(dataToWrite!=None):
            fullFileName = os.path.join(self.DestPath,fileName)
            print "SAVE HTML FILE: " +fullFileName
            #f=open(fileName,"w")
            f=open(fullFileName,"w")
            try:
                dataToWrite = dataToWrite.encode("utf-8")
                f.write(dataToWrite)
            except Exception, error:
                print "Error on saving DATA: " +str(error)
            finally:
                f.close()
        else:
            print "Sorry, NO DATA to write!"

    def extractIconsZip(self):
        try:
            #1) create icons dir in destination path
            iconsDestPath= os.path.join(self.DestPath,"icons")
            #os.path.join()
            if(os.path.exists(iconsDestPath)==False):
                print "Making \'icons\' dir : "+ iconsDestPath +"..."
                os.mkdir(iconsDestPath)
            #2) unzip icons zip into destination path
            # get CURRENT VALID PATH
            scriptPath=os.path.dirname(sys.argv[0])
            iconsZipPath=os.path.join(scriptPath,"icons.zip")
            zFile  = zipfile.ZipFile(iconsZipPath,"r")

            fileNameList=zFile.namelist()
            # i have to extract the same directory structure
            for fileName in fileNameList:
                baseFileName=os.path.basename(fileName)
                buffer =zFile.read(fileName)
                #3) Write icon file
                if(baseFileName!='' and baseFileName!=None):
                    iconFile=os.path.join(iconsDestPath,baseFileName)
                    # HACK: if you don't use wb mode, the PNG files unzipped are INVALID!!!!
                    f = file(iconFile,'wb')
                    f.write(buffer)
                    f.close()
            zFile.close()
        except Exception,err:
            print "FileManager - error in extractIconsZip: "+str(err)

# --------------------------
# HELP, TEST, MAIN functions
# --------------------------

def get_help():
    usage="""
pyFreemind.py MindMap.mm [-d] [--ni] [--sp] [-o result.html]

Purpose: Generate a 'flat' HTML file from MindMap,
         divided into Chapters (chapter==root's first level child);
         useful start point to transform mindmap in plain documentation

NOTE: the file generated is saved into the same dir of MindMap.mm, with a
      dir 'icons' containing the default Freemind icons' files (if -ni option
      is not used)

################## DarioZ script - 12/05/09 ##################
"""
    return usage

def parseArgv(parser):
    #showHelp=False
    debugMode=False
    resultFile="flat.html"
    fileToRead=""
    notIcons=False
    singleParagraph=False

    #new parsing
    parser.add_option("-d",action="store_true",default=False,dest="DEBUG",help="debug mode (print clean data on screen)")
    parser.add_option("-o",dest="OUTPUT",help="output HTML file name (default: MindMap_flat.html)")
    parser.add_option("--ni",action="store_true",default=False,dest="NOTICONS",help="not icons - generate HTML without icons")
    parser.add_option("--sp",action="store_false",default=True,
    dest="SINGLEPARAGRAPH",help="single paragraph - write one HTML paragraph for each node")

    (options, args)=parser.parse_args()
    #print "ARGS LIST:"+str(args)
    debugMode = options.DEBUG
    resultFile=options.OUTPUT
    #print "OUTPUT:" +str(resultFile)
    notIcons=options.NOTICONS
    singleParagraph=options.SINGLEPARAGRAPH
    # Full result path management
    try:
        fileToRead=args[0]
        fileToReadPath=os.path.dirname(fileToRead)
        # output parameter specified
        if(resultFile!='' and resultFile!=None):
            resultFile = os.path.join(fileToReadPath, resultFile)
        else:
            mindMap=os.path.basename(fileToRead)
            #fix passing mindMap with SPACE inner NAME - 26/06/09
            if(" " in mindMap):
                mindMap=mindMap.replace(" ","_")
                print "Fix mindMap name: "+str(mindMap)
            if(mindMap!=None and mindMap!=''):
                (fileName,extension)=os.path.splitext(mindMap) #fix bug names with '.' - 26/06/09
                if(extension!='' and extension!=None):
                    resultFile = os.path.join(fileToReadPath, fileName + "_flat.html")
            print "NO output parameter - RESULTFILE-->:"+resultFile
    except Exception,err:
        print "Error on parseArgv: " +str(err)
        pass
    return (debugMode,fileToRead,resultFile,notIcons,singleParagraph)



def main():
    #new parsing
    usage=get_help()
    parser=OptionParser(usage)
    (debugMode,fileToRead, resultFile,notIcons,singleParagraph) = parseArgv(parser)
    #print "RESULTFILE:"+str(resultFile)
    if(fileToRead!="" and os.path.exists(fileToRead)):
        docGen=FmDocumentGenerator(fileToRead)
        htmlDoc = docGen.analizeTree(debugMode,notIcons,singleParagraph)
        print "Mind map analysis completed"
        fileManager= FileManager(fileToRead)
        fileManager.writeHtmlInUTF8(htmlDoc,resultFile)

        # UNZIP ICONS
        if(notIcons==False):
            print "Extracting icons..."
            fileManager.extractIconsZip()
            print "All done!"
    else:
        print ""
        print "ERROR: The mind map doesn\'t exist!Please, give me a valid file path!!"
        print ""
        parser.print_help()

# -----------------------
# -----------------------

if __name__=='__main__':
    try:
        main()
    except Exception,err:
        print "MAIN ERROR %s" % str(err)